package solver

import (
	"fmt"
	"log"
	"slices"
	"strings"
)

type Node struct {
	cell                 *Cell
	cand1, cand2         int
	chosenCand, linkCand int
	linked               bool
}

func (g *Grid) chooseBiValueNode() []*Node {
	nodes := []*Node{}
	for _, row := range g.rows {
		for _, cell := range row.cells {
			if cell.cands.Count() != 2 {
				continue
			}
			cands := cell.cands.GetList()
			nodes = append(nodes, &Node{cell: cell,
				cand1: cands[0],
				cand2: cands[1]})
		}
	}
	return nodes
}

func (g *Grid) verifyXYChain(node1, node2 *Node, nodes []*Node) (used bool) {
	cands1 := node1.cell.cands
	cands2 := node2.cell.cands
	icands := cands1.Intersect(cands2)
	if icands.Count() != 1 {
		return false
	}
	chosenCand := icands.GetList()[0]
	seenCells1 := g.seenCells(node1.cell)
	seenCells2 := g.seenCells(node2.cell)

	iCells := shareCells(seenCells1, seenCells2)
	canRemoved := false
	for _, cell := range iCells {
		if cell.cands.Has(chosenCand) {
			canRemoved = true
			break
		}
	}
	if !canRemoved {
		return false
	}

	for _, node := range nodes {
		node.linked = false
	}

	node1.chosenCand = cands1.Diff(icands).GetList()[0]
	node1.linkCand = chosenCand

	return findChain(chosenCand, node1, node2, nodes)
}

func findChain(chosenCand int, node, end *Node, nodes []*Node) bool {
	if node == nil {
		return false
	}
	if node.linked {
		return false
	} else {
		node.linked = true
	}

	//fmt.Printf("(%v, %v) chosen=%v link=%v\n", node.cell.rowNum, node.cell.colNum, node.chosenCand+1, node.linkCand+1)
	if *(node.cell) == *(end.cell) && node.chosenCand == chosenCand {
		//fmt.Println("found chain")
		return true
	}

	seenNodes := findSeenNodes(node, nodes)
	for _, seenNode := range seenNodes {
		if findChain(chosenCand, seenNode, end, nodes) {
			return true
		}
	}

	return false

}

func findSeenNodes(from *Node, nodes []*Node) []*Node {
	seenNodes := []*Node{}
	for _, node := range nodes {
		if from.cell.rowNum != node.cell.rowNum && from.cell.colNum != node.cell.colNum && from.cell.boxNum != node.cell.boxNum {
			continue
		}
		if node.linked {
			continue
		}
		if from.chosenCand == node.cand1 {
			node.linkCand = from.chosenCand
			node.chosenCand = node.cand2
		} else if from.chosenCand == node.cand2 {
			node.linkCand = from.chosenCand
			node.chosenCand = node.cand1
		} else {
			continue
		}
		seenNodes = append(seenNodes, node)
	}
	return seenNodes
}
func (g *Grid) XYChain() (used bool) {
	nodes := g.chooseBiValueNode()
	n := len(nodes)
	for i := 0; i < n-1; i++ {
		for j := i + 1; j < n; j++ {
			if !g.verifyXYChain(nodes[i], nodes[j], nodes) {
				continue
			}
			if g.clearupSeenCand(nodes[i].linkCand,
				[]*Cell{nodes[i].cell, nodes[j].cell}) {
				used = true
				log.Printf("r%vc%v, r%vc%v %v XYChain",
					nodes[i].cell.rowNum, nodes[i].cell.colNum,
					nodes[j].cell.rowNum, nodes[j].cell.colNum,
					nodes[i].linkCand+1)
				return used
			}
		}
	}
	return
}

func (g *Grid) RemotePair() (used bool) {
	pairCells := g.chooseRPCells()
	for xy, cells := range pairCells {
		n := len(cells)
		if n < 4 {
			continue
		}
		for i := 0; i < n; i++ {
			for j := 0; j < n; j++ {
				if i == j {
					continue
				}
				if sameHouse([]*Cell{cells[i], cells[j]}) {
					continue
				}
				path := []*Cell{cells[i]}
				path = findRP(path, cells[j], cells)
				//fmt.Println(xy, cells[i], cells[j], path)
				if len(path) == 0 || len(path)%2 != 0 {
					continue
				}
				ok, removeCells := g.clearupRPSeenCands(xy, path)
				if ok {
					used = true
					logRemotePair(path, removeCells, xy[:])
					return
				}
			}
		}
	}
	return
}

func logRemotePair(path, removeCells []*Cell, cands []int) {
	var b strings.Builder
	logCells(&b, path)
	fmt.Fprintf(&b, "%v%v ", toValue(cands[0]), toValue(cands[1]))
	logCells(&b, removeCells)
	fmt.Fprint(&b, "Remote Pair")
	log.Println(b.String())
}
func (g *Grid) clearupRPSeenCands(xy [2]int, path []*Cell) (
	used bool, removeCells []*Cell) {
	n := len(path)
	start := path[0]
	end := path[n-1]
	share := g.seenCells(start)
	share = shareCells(share, g.seenCells(end))

	for _, cell := range share {
		if slices.Contains(path, cell) {
			continue
		}
		if cell.removeCands(xy[:]) {
			removeCells = append(removeCells, cell)
			used = true
		}
	}
	return
}

func (g *Grid) chooseRPCells() (pairCells map[[2]int][]*Cell) {
	pairCells = map[[2]int][]*Cell{}
	for _, row := range g.rows {
		for _, cell := range row.cells {
			if cell.cands.Count() != 2 {
				continue
			}
			cands := [2]int(cell.cands.GetList())
			pairCells[cands] = append(pairCells[cands], cell)
		}
	}
	return
}

func (g *Grid) verifyRPCells(start, end *Cell, bases []*Cell) (used bool) {
	path := []*Cell{start}
	path = findRP(path, end, bases)
	return len(path) != 0
}

func findRP(path []*Cell, end *Cell, bases []*Cell) []*Cell {
	last := path[len(path)-1]
	if last == end {
		return path
	}
	nexts := []*Cell{}
	for _, cell := range bases {
		if slices.Contains(path, cell) {
			continue
		}
		if sameHouse([]*Cell{last, cell}) {
			nexts = append(nexts, cell)
		}
	}
	for _, next := range nexts {
		result := findRP(append(path, next), end, bases)
		if len(result) != 0 {
			return result
		}
	}
	return []*Cell{}
}
